endif
pkgconfig_DATA += src/libostree/ostree-1.pc
+
+if USE_GPGME
+libostree_1_la_LIBADD += $(GPGME_LIBS)
+endif
+
test-pull-archive-z \
test-pull-corruption \
test-pull-resume \
+ test-gpg-signed-commit \
test-admin-deploy-1 \
test-admin-deploy-2 \
test-admin-deploy-uboot \
tests/libtest.sh \
$(NULL)
+gpginsttestdir = $(pkglibexecdir)/installed-tests/gpghome
+gpginsttest_DATA = tests/gpghome/secring.gpg \
+ tests/gpghome/pubring.gpg \
+ tests/gpghome/trustdb.gpg
+
%.test: tests/%.sh Makefile
$(AM_V_GEN) (echo '[Test]' > $@.tmp; \
echo 'Exec=$(pkglibexecdir)/installed-tests/$(notdir $<)' >> $@.tmp; \
])
AM_CONDITIONAL(BUILDOPT_INTROSPECTION, test x$found_introspection = xyes)
+LIBGPGME_DEPENDENCY="1.1.8"
+
+AC_ARG_WITH(gpgme,
+ AS_HELP_STRING([--without-gpgme], [Do not use gpgme]),
+ :, with_gpgme=maybe)
+
+AS_IF([ test x$with_gpgme != xno ], [
+ AC_MSG_CHECKING([for $LIBGPGME_DEPENDENCY])
+ m4_ifdef([AM_PATH_GPGME], [
+ AM_PATH_GPGME($LIBGPGME_DEPENDENCY, have_gpgme=yes, have_gpgme=no)
+ ],[
+ AM_CONDITIONAL([have_gpgme],[false])
+ ])
+ AC_MSG_RESULT([$have_gpgme])
+ AS_IF([ test x$have_gpgme = xno && test x$with_gpgme != xmaybe ], [
+ AC_MSG_ERROR([gpgme is enabled but could not be found])
+ ])
+ AS_IF([ test x$have_gpgme = xyes], [
+ AC_DEFINE(HAVE_GPGME, 1, [Define if we have gpgme])
+ with_gpgme=yes
+ ], [ with_gpgme=no ])
+], [ with_gpgme=no ])
+if test x$with_gpgme != xno; then OSTREE_FEATURES="$OSTREE_FEATURES +gpgme"; fi
+AM_CONDITIONAL(USE_GPGME, test $with_gpgme != no)
+
LIBARCHIVE_DEPENDENCY="libarchive >= 2.8.0"
GTK_DOC_CHECK([1.15], [--flavour no-tmpl])
introspection: $found_introspection
libsoup (retrieve remote HTTP repositories): $with_soup
libarchive (parse tar files directly): $with_libarchive
+ gpgme (sign commits): $with_gpgme
documentation: $enable_gtk_doc
gjs-based tests: $have_gjs
dracut: $with_dracut"
#include <glib-unix.h>
#include <gio/gunixinputstream.h>
+#include <gio/gfiledescriptorbased.h>
#include "otutil.h"
#include "libgsystem.h"
#include "ostree-repo-private.h"
#include "ostree-repo-file.h"
+#ifdef HAVE_GPGME
+#include <locale.h>
+#include <gpgme.h>
+#include <glib/gstdio.h>
+#endif
+
/**
* SECTION:libostree-repo
* @title: Content-addressed object store
return FALSE;
}
#endif
+
+#ifdef HAVE_GPGME
+gboolean
+ostree_repo_sign_commit (OstreeRepo *self,
+ const gchar *commit_checksum,
+ const gchar *key_id,
+ const gchar *homedir,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret = FALSE;
+ gs_unref_object GFile *commit_path = NULL;
+ gs_unref_variant GVariant *metadata = NULL;
+ gs_free gchar *commit_filename = NULL;
+ gs_unref_object GFile *tmp_signature_file = NULL;
+ gs_unref_object GOutputStream *tmp_signature_output = NULL;
+ gs_unref_variant_builder GVariantBuilder *builder = NULL;
+ gs_unref_variant_builder GVariantBuilder *signature_builder = NULL;
+ gs_unref_variant GVariant *commit_variant = NULL;
+ gs_unref_variant GVariant *signaturedata = NULL;
+ gs_unref_bytes GBytes *signature_bytes = NULL;
+ gpgme_ctx_t context;
+ gpgme_engine_info_t info;
+ gpgme_error_t err;
+ gpgme_key_t key = NULL;
+ gpgme_data_t commit_buffer = NULL;
+ gpgme_data_t signature_buffer = NULL;
+ int signature_fd = -1;
+ gpgme_sign_result_t result;
+ GMappedFile *signature_file = NULL;
+
+ if (!ostree_repo_load_variant (self, OSTREE_OBJECT_TYPE_COMMIT,
+ commit_checksum, &commit_variant, error))
+ goto out;
+
+ if (!ostree_repo_read_commit_detached_metadata (self,
+ commit_checksum,
+ &metadata,
+ cancellable,
+ error))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Unable to read existing detached metadata");
+ goto out;
+ }
+
+ if (!gs_file_open_in_tmpdir (self->tmp_dir, 0644,
+ &tmp_signature_file, &tmp_signature_output,
+ cancellable, error))
+ goto out;
+
+ gpgme_check_version (NULL);
+ gpgme_set_locale (NULL, LC_CTYPE, setlocale (LC_CTYPE, NULL));
+
+ if ((err = gpgme_new (&context)) != GPG_ERR_NO_ERROR)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Unable to create gpg context");
+ goto out;
+ }
+
+ info = gpgme_ctx_get_engine_info (context);
+
+ if (homedir != NULL)
+ {
+ if ((err = gpgme_ctx_set_engine_info (context, info->protocol, info->file_name, homedir))
+ != GPG_ERR_NO_ERROR)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Unable to set gpg homedir");
+ goto out;
+ }
+ }
+
+ /* Get the secret keys with the given key id */
+ if ((err = gpgme_get_key (context, key_id, &key, 1)) != GPG_ERR_NO_ERROR)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "No gpg key found with the given key-id");
+ goto out;
+ }
+
+ /* Add the key to the context as a signer */
+ if ((err = gpgme_signers_add (context, key)) != GPG_ERR_NO_ERROR)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Error signing commit");
+ goto out;
+ }
+
+ if ((err = gpgme_data_new_from_mem (&commit_buffer, g_variant_get_data (commit_variant),
+ g_variant_get_size (commit_variant), FALSE)) != GPG_ERR_NO_ERROR)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Failed to create buffer from commit file");
+ goto out;
+ }
+
+ signature_fd = g_file_descriptor_based_get_fd ((GFileDescriptorBased*)tmp_signature_output);
+ if (signature_fd < 0)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Unable to open signature file");
+ goto out;
+ }
+
+ if ((err = gpgme_data_new_from_fd (&signature_buffer, signature_fd)) != GPG_ERR_NO_ERROR)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Failed to create buffer for signature file");
+ goto out;
+ }
+
+ if ((err = gpgme_op_sign (context, commit_buffer, signature_buffer, GPGME_SIG_MODE_DETACH))
+ != GPG_ERR_NO_ERROR)
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Failure signing commit file");
+ goto out;
+ }
+
+ result = gpgme_op_sign_result (context);
+
+ if (!g_output_stream_close (tmp_signature_output, cancellable, error))
+ goto out;
+
+ signature_file = gs_file_map_noatime (tmp_signature_file, cancellable, error);
+ if (!signature_file)
+ goto out;
+ signature_bytes = g_mapped_file_get_bytes (signature_file);
+
+ // Now read the file and put its contents into the result GVariant
+ if (metadata)
+ {
+ builder = ot_util_variant_builder_from_variant (metadata, G_VARIANT_TYPE ("a{sv}"));
+ signaturedata = g_variant_lookup_value (metadata, "ostree.gpgsigs", G_VARIANT_TYPE ("aay"));
+ if (signaturedata)
+ signature_builder = ot_util_variant_builder_from_variant (signaturedata, G_VARIANT_TYPE ("aay"));
+ }
+ if (!builder)
+ builder = g_variant_builder_new (G_VARIANT_TYPE ("a{sv}"));
+ if (!signature_builder)
+ signature_builder = g_variant_builder_new (G_VARIANT_TYPE ("aay"));
+
+ g_variant_builder_add (signature_builder, "@ay", ot_gvariant_new_ay_bytes (signature_bytes));
+
+ g_variant_builder_add (builder, "{sv}", "ostree.gpgsigs", g_variant_builder_end (signature_builder));
+
+ metadata = g_variant_builder_end (builder);
+
+ if (!ostree_repo_write_commit_detached_metadata (self,
+ commit_checksum,
+ metadata,
+ cancellable,
+ error))
+ {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "Unable to read existing detached metadata");
+ goto out;
+ }
+
+ ret = TRUE;
+out:
+ if (commit_buffer)
+ gpgme_data_release (commit_buffer);
+ if (signature_buffer)
+ gpgme_data_release (signature_buffer);
+ if (key)
+ gpgme_key_release (key);
+ if (context)
+ gpgme_release (context);
+ if (signature_file)
+ g_mapped_file_unref (signature_file);
+ return ret;
+}
+
+#endif
#pragma once
+#include "config.h"
#include "ostree-core.h"
#include "ostree-types.h"
GCancellable *cancellable,
GError **error);
+#ifdef HAVE_GPGME
+gboolean ostree_repo_sign_commit (OstreeRepo *self,
+ const gchar *commit_checksum,
+ const gchar *key_id,
+ const gchar *homedir,
+ GCancellable *cancellable,
+ GError **error);
+#endif
+
G_END_DECLS
return (GInputStream*)ret;
}
+GVariantBuilder *
+ot_util_variant_builder_from_variant (GVariant *variant,
+ const GVariantType *type)
+{
+ GVariantBuilder *builder = NULL;
+ gint i, n;
+
+ builder = g_variant_builder_new (type);
+
+ n = g_variant_n_children (variant);
+ for (i = 0; i < n; i++)
+ {
+ GVariant *child = g_variant_get_child_value (variant, i);
+ g_variant_builder_add_value (builder, child);
+ g_variant_unref (child);
+ }
+
+ return builder;
+}
+
GInputStream *ot_variant_read (GVariant *variant);
+GVariantBuilder *ot_util_variant_builder_from_variant (GVariant *variant,
+ const GVariantType *type);
+
G_END_DECLS
static gint opt_owner_uid = -1;
static gint opt_owner_gid = -1;
static gboolean opt_table_output;
+#ifdef HAVE_GPGME
+static char **opt_key_ids;
+static char *opt_gpg_homedir;
+#endif
static GOptionEntry options[] = {
{ "subject", 's', 0, G_OPTION_ARG_STRING, &opt_subject, "One line subject", "subject" },
{ "skip-if-unchanged", 0, 0, G_OPTION_ARG_NONE, &opt_skip_if_unchanged, "If the contents are unchanged from previous commit, do nothing", NULL },
{ "statoverride", 0, 0, G_OPTION_ARG_FILENAME, &opt_statoverride_file, "File containing list of modifications to make to permissions", "path" },
{ "table-output", 0, 0, G_OPTION_ARG_NONE, &opt_table_output, "Output more information in a KEY: VALUE format", NULL },
+#ifdef HAVE_GPGME
+ { "gpg-sign", 0, 0, G_OPTION_ARG_STRING_ARRAY, &opt_key_ids, "GPG Key ID to sign the commit with", "key-id"},
+ { "gpg-homedir", 0, 0, G_OPTION_ARG_STRING, &opt_gpg_homedir, "GPG Homedir to use when looking for keyrings", "homedir"},
+#endif
{ NULL }
};
goto out;
}
+#ifdef HAVE_GPGME
+ if (opt_key_ids)
+ {
+ char **iter;
+
+ for (iter = opt_key_ids; iter && *iter; iter++)
+ {
+ const char *keyid = *iter;
+
+ if (!ostree_repo_sign_commit (repo,
+ commit_checksum,
+ keyid,
+ opt_gpg_homedir,
+ cancellable,
+ error))
+ goto out;
+ }
+ }
+#endif
+
ostree_repo_transaction_set_ref (repo, NULL, opt_branch, commit_checksum);
if (!ostree_repo_commit_transaction (repo, &stats, cancellable, error))
export G_DEBUG=fatal-warnings
+export TEST_GPG_KEYID="472CDAFA"
+export TEST_GPG_HOME=${SRCDIR}/gpghome
+
if test -n "${OT_TESTS_DEBUG}"; then
set -x
fi
--- /dev/null
+#!/bin/bash
+#
+# Copyright (C) 2013 Jeremy Whiting <jeremy.whiting@collabora.com>
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the
+# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+# Boston, MA 02111-1307, USA.
+
+set -e
+
+if ! ostree --version | grep -q -e '\+gpgme'; then
+ exit 77
+fi
+
+. $(dirname $0)/libtest.sh
+
+setup_test_repository "archive-z2"
+
+cd ${test_tmpdir}
+${OSTREE} commit -b test2 -s "A GPG signed commit" -m "Signed commit body" --gpg-sign=${TEST_GPG_KEYID} --gpg-homedir=${TEST_GPG_HOME} --tree=dir=files
+$OSTREE show --print-detached-metadata-key=ostree.gpgsigs test2 > test2-gpgsigs
+# We at least got some content here and ran through the code; later
+# tests will actually do verification
+assert_file_has_content test2-gpgsigs 'byte '
+
+# Now sign a commit 3 times (with the same key)
+cd ${test_tmpdir}
+${OSTREE} commit -b test2 -s "A GPG signed commit" -m "Signed commit body" --gpg-sign=${TEST_GPG_KEYID} --gpg-sign=${TEST_GPG_KEYID} --gpg-sign=${TEST_GPG_KEYID} --gpg-homedir=${TEST_GPG_HOME} --tree=dir=files
+$OSTREE show --print-detached-metadata-key=ostree.gpgsigs test2 > test2-gpgsigs
+assert_file_has_content test2-gpgsigs 'byte '